﻿using FarseerPhysics.Collision.Shapes;
using FarseerPhysics.Common;
using FarseerPhysics.Factories;
using FarseerPhysics.Dynamics;
using System;
using System.Collections.Generic;
using System.Threading;
using BHG.System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using FarseerPhysics.Collision;
using FarseerPhysics.Dynamics.Contacts;
using SkinnedModel;


namespace BHG.System
{

    class GamePlayer : GameObject
    {
        const float MASS_MULTIPLIER = 20.0f;
        const float ROTATION_SPEED = 0.1f;
        const float ROTATION_LIMIT_LEFT = 2.0f;
        const float ROTATION_LIMIT_RIGHT = 4.6f;
        const float ANIMATION_CHANGE_SPEED = 0.01f;
        const float MAX_VELOCITY = 25f;
        const float VELOCITY_CHANGE = 0.3f;
        const float VELOCITY_JUMP = 20f;
        private float MAX_STEPS = 0f;
        private bool WasJump { get; set; }

        Fixture feet;
        public int Contacts { get; set; }

        public float StepTimer { get; set; }

        public AnimationPlayer AnimationPlayer { get; set; }

        public bool DoJump()
        {
            if (WasJump)
            {
                WasJump = false;
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Create player and add him to physics
        /// </summary>
        /// <param name="position"></param>
        public GamePlayer(Vector2 position, Model model)
        {
            // model and physics body
            WasJump = false;
            this.Model = model;
            this.body = BodyFactory.CreateBody(PhysicsEngine.Instance.gameWorld);

            SkinningData skinningData = model.Tag as SkinningData;

            if (skinningData == null) throw new InvalidOperationException("This model does not contain a SkinningData tag.");

            AnimationPlayer = new AnimationPlayer(skinningData);
            AnimationClip clip = skinningData.AnimationClips["walk"];
            MAX_STEPS = (float)clip.Duration.TotalSeconds / 2;
            StepTimer = MAX_STEPS;
            AnimationPlayer.StartClip(clip);

            // player's "body"
            Vertices sh = PolygonTools.CreateRectangle(2.0f / 2, 9.0f / 2);
            Vector2 trans = new Vector2(0.0f, 0.02f);
            sh.Translate(ref trans);
            this.body.CreateFixture(new PolygonShape(sh));

            // player's "feet"
            sh = PolygonTools.CreateRectangle(1.95f / 2, 0.04f / 2);
            trans = new Vector2(0.0f, -9f / 2);
            sh.Translate(ref trans);
            feet = this.body.CreateFixture(new PolygonShape(sh));

            // delegates to count contacts
            feet.OnCollision += OnFeetCollision;
            feet.OnSeparation += OnFeetSeparation;

            this.body.Position = position;
            this.body.BodyType = FarseerPhysics.Dynamics.BodyType.Dynamic;
            this.body.Mass *= MASS_MULTIPLIER;

            this.body.UserData = this;
            this.Scale = 0.12f;
            this.RotationZ = 4.3f;
            this.body.LinearVelocity = new Vector2(0.1f, 0f);
        }

        public float getAnimationSpeed()
        {
            return (Math.Abs(this.body.LinearVelocity.X) / MAX_VELOCITY) * 3f;
        }

        public bool stepSound(GameTime gTime)
        {
            StepTimer -= (float)gTime.ElapsedGameTime.TotalSeconds * getAnimationSpeed();
            if (StepTimer < 0)
            {
                StepTimer = MAX_STEPS;
                return CanJump();
            }
            return false;
        }

        /// <summary>
        /// Update player each game update
        /// </summary>
        /// <param name="gTime">Game time</param>
        public override void Update(GameTime gTime)
        {
            if (getAnimationSpeed() > 0)
            {
                AnimationPlayer.Update(new TimeSpan((long)(gTime.ElapsedGameTime.Ticks * getAnimationSpeed())), true, Matrix.Identity);
            }

            // always head up
            this.Rotation = 0.0f;
        }

        /// <summary>
        /// Called everytime feet touch something, used to count contacts.
        /// </summary>
        /// <param name="fixtureA"></param>
        /// <param name="fixtureB"></param>
        /// <param name="manifold"></param>
        /// <returns></returns>
        protected bool OnFeetCollision(Fixture fixtureA, Fixture fixtureB, Contact manifold)
        {
            Contacts++;
            return true;
        }
        /// <summary>
        /// Called everytime something separates from feet.
        /// </summary>
        /// <param name="fixtureA"></param>
        /// <param name="fixtureB"></param>
        protected void OnFeetSeparation(Fixture fixtureA, Fixture fixtureB)
        {
            Contacts--;
        }

        /// <summary>
        /// What to do when buttons are pressed
        /// </summary>
        /// <param name="input">Input of everything</param>
        /// <param name="playerIndex">Index of player</param>
        public void HandleInput(GameStateManagement.InputState input, int playerIndex)
        {
            KeyboardState kState = input.CurrentKeyboardStates[playerIndex];
            KeyboardState prevKState = input.LastKeyboardStates[playerIndex];

            if (kState.IsKeyDown(Keys.Left)) { MoveLeft(); }
            if (kState.IsKeyDown(Keys.Right)) { MoveRight(); }
            if ((kState.IsKeyDown(Keys.Up)) && (prevKState.IsKeyDown(Keys.Up) == false)) { Jump(); }
            prevKState = kState;
        }

        /// <summary>
        /// Move player left
        /// </summary>
        public void MoveLeft()
        {
            float tempRotation;
            float tempVelocity;
            if (CanJump())
            {
                tempVelocity = VELOCITY_CHANGE;
                tempRotation = ROTATION_SPEED;
            }
            else
            {
                tempVelocity = VELOCITY_CHANGE / (float)3;
                tempRotation = ROTATION_SPEED / (float)3;
            }
            if (this.body.LinearVelocity.X > -MAX_VELOCITY)
            {
                this.body.LinearVelocity = new Vector2(this.body.LinearVelocity.X - tempVelocity, this.body.LinearVelocity.Y);
            }
            if (this.RotationZ > ROTATION_LIMIT_LEFT)
            {
                RotationZ -= tempRotation;
            }
        }

        /// <summary>
        /// Move player right
        /// </summary>
        public void MoveRight()
        {
            float tempRotation;
            float tempVelocity;
            if (CanJump())
            {
                tempVelocity = VELOCITY_CHANGE;
                tempRotation = ROTATION_SPEED;
            }
            else
            {
                tempVelocity = VELOCITY_CHANGE / (float)3;
                tempRotation = ROTATION_SPEED / (float)3;
            }
            if (this.body.LinearVelocity.X < MAX_VELOCITY)
            {
                this.body.LinearVelocity = new Vector2(this.body.LinearVelocity.X + tempVelocity, this.body.LinearVelocity.Y);
            }
            if (this.RotationZ < ROTATION_LIMIT_RIGHT)
            {
                RotationZ += tempRotation;
            }

        }

        /// <summary>
        /// Jump
        /// </summary>
        public void Jump()
        {
            if (CanJump())
            {
                this.body.LinearVelocity = new Vector2(this.body.LinearVelocity.X, this.body.LinearVelocity.Y + VELOCITY_JUMP);
                this.WasJump = true;
            }
        }

        /// <summary>
        /// Bool function testing player position
        /// </summary>
        /// <returns>true if player can jump</returns>
        public bool CanJump()
        {
            // when player's feet are touching ground
            if (Contacts > 0) return true;
            return false;
        }

        public float strengOfStep()
        {
            float temp = (Math.Abs(this.body.LinearVelocity.X) / MAX_VELOCITY) + 0.3f;
            if (temp > 1)
            {
                temp = 1;
            }
            return temp;
        }

        public void moveALittleUp()
        {
            this.body.Position = new Vector2(this.body.Position.X, this.body.Position.Y + 0.01f);
        }

    }
}
